/*
 * COPYRIGHT:	2010 Christopher Smith, licenced under the GNU GPL v2; see COPYING in the top level directory
 * PROJECT:		sdedit
 * FILE:		menu enum.cpp
 * PURPOSE:		a menu interface to lookup.cpp's enumerations
 *				
 */

namespace MenuEnum
{
using namespace Lookup;

template <class T> struct DisplayName
{
	typedef void (__stdcall *ReturnFunc)(PVOID Context, PCWSTR DisplayText);
	typedef void (__stdcall *GetFunc)(PCWSTR Name, T const &Data, PVOID Context, ReturnFunc Return);
};

template <class T> struct ReturnData
{
	PCWSTR Name;
	T const *Data;
};

template <class T> class MenuBase;

template <class T> class PopupMenu : Lookup::ChildrenWalkDest<T>, EntriesWalkDest<T>, public auto_refctd_std_base
{
	friend MenuBase<T>;

	MenuBase<T> *const Base;
	HMENU Menu;
	BOOL Filled;
	auto_refct<Enumerator<T> > Enum;
	ULONG ItemCt;

	PopupMenu(MenuBase<T> *Base, Enumerator<T> *Enum) :
		Base(Base),
		Enum(Enum),
		Filled(FALSE),
		ItemCt(0),
		Menu(CreatePopupMenu())
	{
		if(!Base)
			RaiseInternalError();
	}

	static void AddMenuItem(PVOID Context, PCWSTR Text)
	{
		PopupMenu *Pm = reinterpret_cast<PopupMenu *>(Context);
		size_t ThisIndex = Pm->Base->ElementData.get_ct();
		Pm->ItemCt++;

		AppendMenu(Pm->Menu, MF_STRING | (Pm->ItemCt % Pm->Base->BreakFactor == 0 ? MF_MENUBREAK : 0), ThisIndex, Text);
	}

	static void FillExceptionProc(PCWSTR Title, PCWSTR Text, PVOID Context)
	{
		HMENU Menu = reinterpret_cast<HMENU>(Context);

		AppendMenu(Menu, MF_DISABLED | MF_GRAYED | MF_STRING, 0, Text);
	}

	int HandleFillException(EXCEPTION_POINTERS *ExceptionPointers, HMENU Menu)
	{
		if(ExceptionPointers->ExceptionRecord->ExceptionCode == STATUS_CANCEL_ONE)
			return EXCEPTION_EXECUTE_HANDLER;

		if(PushLayeredErrorText(ExceptionPointers, &FillExceptionProc, Menu))
			RaiseCancelOne();

		return EXCEPTION_CONTINUE_SEARCH; //unknown error
	}

public:
	void Fill()
	{
		if(!Filled)
		{
			__try
			{
				Enum->WalkChildren(*this);
				Enum->WalkEntries(*this);
			}
			__except(HandleFillException(exception_info(), Menu))
			{}
			Filled = TRUE;
		}
	}

	virtual void ChildProc(PCWSTR Name, Enumerator<T> *Child) override
	{
		auto_refct<PopupMenu> &ChildMenu = *Base->Menus.next();
		ChildMenu = new PopupMenu(Base, Child);
		RaiseOnWin32ErrorT(AppendMenu(Menu, MF_POPUP, c_cast<ULONG_PTR>(ChildMenu->Menu), Name));
	}

	virtual void EntryProc(PCWSTR Name, T const &Data) override
	{
		*Base->ElementData.next() = &Data;
		*Base->ElementNames.next() = Name;

		if(Base->DisplayFunc)
			Base->DisplayFunc(Name, Data, this, &AddMenuItem);
		else
			AddMenuItem(this, Name);
	}

	virtual void BreakProc()
	{
		RaiseOnWin32ErrorT(AppendMenu(Menu, MF_SEPARATOR, 0, 0));
	}

	HMENU GetHMENU()
	{
		return Menu;
	}

	~PopupMenu()
	{
		if(Menu)
			DestroyMenu(Menu);
	}

	PopupMenu &operator =(PopupMenu const &);
};

template <class T> class MenuBase
{
	friend PopupMenu<T>;

	typename DisplayName<T>::GetFunc DisplayFunc;
	auto_array<auto_refct<PopupMenu<T> > > Menus;
	auto_array<auto_szpod<T> > ElementData;
	auto_array<auto_szpod<WCHAR> > ElementNames;
	ULONG BreakFactor;

public:
	MenuBase(Enumerator<T> *Enum, typename DisplayName<T>::GetFunc DisplayFunc OPTIONAL, ULONG BreakFactor) :
		DisplayFunc(DisplayFunc),
		BreakFactor(BreakFactor)
	{
		*Menus.next() = new PopupMenu<T>(this, Enum);
	}

	PopupMenu<T> *TopMenu()
	{
		return Menus + 0;
	}

	void ShowMenu(HWND Parent, DialogCallback &Callback, int x, int y)
	{
		ULONG OptId = TrackPopupMenu(Menus[0]->Menu, TPM_RETURNCMD, x, y, 0, Parent, NULL);

		if(OptId == 0)
		{
			Callback.SendCallback(DN_CLOSE_CANCEL, NULL);
			return;
		}

		OptId--; //remove offset of 1 added to distinguish error from first item

		ReturnData<T> Rd = {ElementNames[OptId], ElementData[OptId]};

		Callback.SendCallback(DN_CLOSE_OK, &Rd);
	}

	void FillSubMenu(HMENU SearchMenu)
	{
		for(size_t i = 0; i < Menus.get_ct(); i++)
			if(Menus[i]->Menu == SearchMenu)
			{
				Menus[i]->Fill();
				return;
			}
	}
};

}//namespace MenuEnum